#include "spl.includes.h"
#include "splib.h"

using namespace SPLib;
using namespace SPLib::Geom;
using namespace SPLib::Layout;

#define DRAGSQUARE_SIZE                     35
#define DRAGSQUARE_OFFSET                   5
#define DOCK_MINWIDTH                       100
#define DOCK_MINHEIGHT                      50
#define SIZEBAR_SIZE                        6

// combined with LAYOUT_* flags
#define STOREDLAYOUTFLAGS_END               0x1000
#define STOREDLAYOUTFLAGS_CHILDS            0x2000

#define DRAGWINDOWFLAGS_ROOT                0x1

static HANDLE           layoutEvent         = NULL;

static TCHAR            szDragWindow[]      = _T("spl.layout.dragWindow");
static TCHAR            szResizeWindow[]    = _T("spl.layout.resizeWindow");
static TCHAR            szLayoutEvent[]     = _T("spl.layout.event");

static HANDLE           dragThread          = NULL;
static DWORD            dragThreadID;
static HWND             dragSourceHandle;
static LayoutElement*   dragSource;
static LayoutElement*   dragTarget;
static LayoutElement*   dragRoot;
static LayoutDock       dragDock;

static LayoutElement*   resizeElem          = NULL;
static LayoutElement*   resizeParent        = NULL;
static HANDLE           resizeThread        = NULL;
static DWORD            resizeThreadID;
static Point            resizeStart;
static Bounds           resizeLimits;
static Bounds           resizeBounds;
static LONG             resizeOld;
static LONG             resizeNew;
static HWND             resizeWindow;

LayoutElement*  LayoutElement::addElement(
  LayoutElement* value
  )
{
  // processed element
  LayoutElement* elem = childs;
  // previous element
  LayoutElement* pre = NULL;

  // find last child pointer
  while (elem)
  {
    // if next child is set to fill, abort
    // they must be kept at the end of the list
    if (elem->dock == DockFill) break;

    // process next child
    pre = elem;
    elem = elem->next;
  }

  // add given element
  if (pre)
    pre->next = value;
  else
    childs = value;
  // set next element
  value->next = elem;

  // return element
  return value;
}

LayoutElement*  LayoutElement::addElement(
  POINT*              position,
  SIZE*               size,
  LayoutDock          dock,
  HWND                handle
  )
{
  // create new element
  LayoutElement* elem = new LayoutElement();

  // set element properties
  elem->childs        = NULL;
  elem->position      = *position;
  elem->size          = *size;
  elem->dock          = dock;
  elem->handle        = handle;
  elem->dragWindow    = NULL;
  elem->style         = LayoutFixed;
  elem->flags         = LAYOUT_ANCHOR_DEFAULT;
  elem->fillElement   = NULL;
  elem->padding.setEmpty();
  elem->margins.setEmpty();

  // add and return element
  return addElement(elem);
}

LayoutElement*  LayoutElement::removeElement(HWND value)
{
  // if window handle invalid, abort
  if (!value) return NULL;

  // processed element
  LayoutElement* elem = childs;
  // previous element
  LayoutElement* pre = NULL;
  // removed element
  LayoutElement* removed;

  // for each child
  while (elem)
  {
    // if matches given handle
    if (elem->handle == value)
    {
      // hide window
      ShowWindow(value, SW_HIDE);

      // remove element from childs
      if (pre)
        pre->next = elem->next;
      else
        childs = elem->next;

      // return removed element
      elem->next = NULL;
      return elem;
    }
    else if (removed = elem->removeElement(value))
    {
      // removed from element childs
      // if no more children
      if (!elem->childs)
      {
        // if element is container
        if (elem->style == LayoutContainer)
        {
          // remove element from children list
          if (pre)
            pre->next = elem->next;
          else
            childs = elem->next;

          // delete element
          delete elem;
        }
      }
      else if (!elem->childs->next)
      {
        // element got only one child
        // if element is container
        if (elem->style == LayoutContainer)
        {
          LayoutElement* removed = elem;

          // replace element by its child
          elem = removed->childs;
          elem->next = removed->next;

          // put element in childen list
          if (pre)
            pre->next = elem;
          else
            childs = elem;

          // TODO: fix element auto-fill

          // set removed element dock to its child
          elem->dock = removed->dock;
          // delete removed element
          delete removed;
        }
      }

      // return removed element
      return removed;
    }

    // process next child
    pre = elem;
    elem = elem->next;
  }

  // element not found
  return NULL;
}

LayoutElement*  LayoutElement::findElement(
  HWND iValue,
  LayoutElement** oParent
  )
{
  // if window handle invalid, abort
  if (!iValue) return NULL;

  // processed element
  LayoutElement* elem = childs;
  // element found
  LayoutElement* target = NULL;

  // for each child
  while (elem)
  {
    // if matches given handle
    if (elem->handle == iValue)
    {
      // return element and its parent
      if (oParent) *oParent = this;
      return elem;
    }

    // if found in element children list, return result
    if (target = elem->findElement(iValue))
      return target;

    // process next child
    elem = elem->next;
  }

  // element not found
  return NULL;
}

void  LayoutElement::drawDragSquare(
  Gdip::Graphics &g,
  LayoutDock dock,
  DragWindowData* data
  )
{
  COLORREF foreColor  = data->dock == dock ? 0x7FFFFFFF : 0x7F000000;
  Gdip::PointF arrow[POINT_COUNT_ARROW];
  Gdip::RectF bounds;

  // get square bounds
  getDragSquare(data, dock, bounds);

  // draw square background
  g.FillRectangle(&Gdip::SolidBrush(
    data->dock == dock ? 0x7B000000 : 0x7BFFFFFF), bounds);
  g.DrawRectangle(&Gdip::Pen(foreColor, 2.0f), bounds);

  // shrink square bounds
  bounds.Inflate(-6.0f, -6.0f);

  // get dock arrow and draw necessary shapes
  switch (dock)
  {
  case DockLeft:
    if (data->flags & DRAGWINDOWFLAGS_ROOT)
    {
      g.FillRectangle(&Gdip::SolidBrush(foreColor),
        bounds.X - 2.0f, bounds.Y,
        4.0f, bounds.Height);
      bounds.X += 2.0f;
    }

    GetLeftArrow(arrow, bounds);
    break;
  case DockRight:
    if (data->flags & DRAGWINDOWFLAGS_ROOT)
    {
      g.FillRectangle(&Gdip::SolidBrush(foreColor),
        bounds.X + bounds.Width - 2.0f, bounds.Y,
        4.0f, bounds.Height);
      bounds.X -= 2.0f;
    }

    GetRightArrow(arrow, bounds);
    break;
  case DockTop:
    if (data->flags & DRAGWINDOWFLAGS_ROOT)
    {
      g.FillRectangle(&Gdip::SolidBrush(foreColor),
        bounds.X, bounds.Y - 2.0f,
        bounds.Width, 4.0f);
      bounds.Y += 2.0f;
    }

    GetUpArrow(arrow, bounds);
    break;
  case DockBottom:
    if (data->flags & DRAGWINDOWFLAGS_ROOT)
    {
      g.FillRectangle(&Gdip::SolidBrush(foreColor),
        bounds.X, bounds.Y + bounds.Height - 2.0f,
        bounds.Width, 4.0f);
      bounds.Y -= 2.0f;
    }

    GetDownArrow(arrow, bounds);
    break;
  }

  if (dock == DockFill)
  {
    // draw square
    g.FillRectangle(&Gdip::SolidBrush(foreColor), bounds);
  }
  else
  {
    // draw arrow
    g.SetPixelOffsetMode(Gdip::PixelOffsetModeHalf);
    g.FillPolygon(&Gdip::SolidBrush(foreColor), arrow, 7);
  }
}

void  LayoutElement::getDragSquare(
  DragWindowData* data,
  LayoutDock dock,
  Gdip::RectF &bounds
  )
{
  // drag region position
  Gdip::PointF offset(
    (Gdip::REAL)((data->cache->GetWidth() - DRAGSQUARE_SIZE * 3) / 2),
    (Gdip::REAL)((data->cache->GetHeight() - DRAGSQUARE_SIZE * 3) / 2));

  // return drag square bounds
  switch (dock)
  {
  case DockLeft:
    bounds = Gdip::RectF(
      offset.X - DRAGSQUARE_OFFSET,
      offset.Y + DRAGSQUARE_SIZE,
      DRAGSQUARE_SIZE, DRAGSQUARE_SIZE);
    break;
  case DockRight:
    bounds = Gdip::RectF(
      offset.X + DRAGSQUARE_SIZE * 2 + DRAGSQUARE_OFFSET,
      offset.Y + DRAGSQUARE_SIZE,
      DRAGSQUARE_SIZE, DRAGSQUARE_SIZE);
    break;
  case DockTop:
    bounds = Gdip::RectF(
      offset.X + DRAGSQUARE_SIZE,
      offset.Y - DRAGSQUARE_OFFSET,
      DRAGSQUARE_SIZE, DRAGSQUARE_SIZE);
    break;
  case DockBottom:
    bounds = Gdip::RectF(
      offset.X + DRAGSQUARE_SIZE,
      offset.Y + DRAGSQUARE_SIZE * 2 + DRAGSQUARE_OFFSET,
      DRAGSQUARE_SIZE, DRAGSQUARE_SIZE);
    break;
  case DockFill:
    if (!data->layout->fillElement && data->layout->style != LayoutFixed)
    {
      bounds = Gdip::RectF(
        offset.X + DRAGSQUARE_SIZE,
        offset.Y + DRAGSQUARE_SIZE,
        DRAGSQUARE_SIZE, DRAGSQUARE_SIZE);
    }
    break;
  }
}

void  LayoutElement::getDragPreview(
  DragWindowData* data,
  LayoutDock dock,
  Gdip::RectF &bounds
  )
{
  // return drag square bounds
  switch (dock)
  {
  case DockRight:
    bounds.X = bounds.Width - dragSource->size.cx;
  case DockLeft:
    bounds.Width = (Gdip::REAL)dragSource->size.cx;
    break;
  case DockBottom:
    bounds.Y = bounds.Height - dragSource->size.cy;
  case DockTop:
    bounds.Height = (Gdip::REAL)dragSource->size.cy;
    break;
  }

  if (bounds.X < 0 || bounds.Width > data->cache->GetWidth())
  {
    bounds.X = 0;
    bounds.Width = (Gdip::REAL)data->cache->GetWidth();
  }

  if (bounds.Y < 0 || bounds.Height > data->cache->GetHeight())
  {
    bounds.Y = 0;
    bounds.Height = (Gdip::REAL)data->cache->GetHeight();
  }
}

void  LayoutElement::renderDragWindow(
  DragWindowData* data
  )
{
  // render on window cache
  Gdip::Graphics g(data->cache);
  // clear cache
  g.Clear(0x00000000);

  // draw drag preview
  if (data->dock)
  {
    Gdip::RectF preview = Gdip::RectF(0, 0,
      (Gdip::REAL)data->cache->GetWidth(),
      (Gdip::REAL)data->cache->GetHeight());

    getDragPreview(data, data->dock, preview);
    preview.Inflate(-1, -1);

    g.FillRectangle(&Gdip::SolidBrush(0x7FFFFFFF), preview);
    g.DrawRectangle(&Gdip::Pen(0x7F000000, 2.0f), preview);
  }

  // draw drag squares
  for (int dock = (int)DockFirst; dock <= (int)DockLast; ++dock)
    drawDragSquare(g, (LayoutDock)dock, data);
}

LayoutDock  LayoutElement::hitTestDragWindow(
  DragWindowData* data,
  Point &mouse
  )
{
  for (int dock = (int)DockFirst; dock <= (int)DockLast; ++dock)
  {
    Gdip::RectF bounds;

    getDragSquare(data, (LayoutDock)dock, bounds);

    if (bounds.Contains((Gdip::REAL)mouse.x, (Gdip::REAL)mouse.y))
    {
      return (LayoutDock)dock;
    }
  }

  return DockNone;
}

void  LayoutElement::dragWndUpdate(HWND hWnd)
{
  // window blend function
  BLENDFUNCTION wndBlend;
  // window cache HBITMAP
  HBITMAP hCache;
  // window DC
  HDC wndDC = GetDC(hWnd);
  // window background DC
  HDC wndBackDC = CreateCompatibleDC(wndDC);

  Gdip::Bitmap* bmpCache = ((DragWindowData*)
    GetWindowLongPtr(hWnd, GWLP_USERDATA))->cache;

  // setup window blend
  wndBlend.AlphaFormat = AC_SRC_ALPHA;
  wndBlend.SourceConstantAlpha = 0xFF;
  wndBlend.BlendFlags = 0;
  wndBlend.BlendOp = AC_SRC_OVER;

  // get window HBITMAP
  if (bmpCache->GetHBITMAP(0x00000000, &hCache) == Gdiplus::Ok)
  {
    // select HBITMAP into window background DC
    SelectObject(wndBackDC, hCache);

    Geom::Rect wndRect;
    GetWindowRect(hWnd, &wndRect);

    Geom::Bounds bounds;
    wndRect.toBounds(&bounds);

    // set window background
    UpdateLayeredWindow(hWnd, NULL, &bounds.point, &bounds.size, wndBackDC,
      &Geom::Point(0, 0), 0x00000000, &wndBlend, ULW_ALPHA);

    // delete window cache HBITMAP
    DeleteObject(hCache);
  }

  // release allocated ressources
  ReleaseDC(hWnd, wndDC);
  DeleteDC(wndBackDC);
}

LRESULT LayoutElement::dragWndProc(
  HWND hWnd,
  UINT msg,
  WPARAM wParam,
  LPARAM lParam)
{
  // drag window data
  DragWindowData* data = (DragWindowData*)
    GetWindowLongPtr(hWnd, GWLP_USERDATA);

  switch (msg)
  {
  case WM_CREATE:
    // associate data to window
    SetWindowLongPtr(hWnd, GWLP_USERDATA, (LONG)(data =
      (DragWindowData*)((LPCREATESTRUCT)lParam)->lpCreateParams));
    return TRUE;
  case WM_DESTROY:
    delete data->cache;
    delete data;

    PostQuitMessage(0);
    return TRUE;
  case WM_ERASEBKGND:
    return TRUE;
  case WM_NCCALCSIZE:
    return HTCLIENT;
  case WM_MOUSEMOVE:
    {
      LayoutDock old = data->dock;

      // find drag square under mouse position
      data->dock = hitTestDragWindow(data, Point(lParam));

      // update drag target
      if (data->dock)
      {
        dragDock    = data->dock;
        dragTarget  = data->layout;
      }
      else if (dragTarget == data->layout)
      {
        dragTarget  = NULL;
        dragDock    = DockNone;
      }

      // if selected dock changed, re-render drag window
      if (data->dock != old)
      {
        renderDragWindow(data);
        dragWndUpdate(hWnd);
      }
    }
    return TRUE;
  default:
    return DefWindowProc(hWnd, msg, wParam, lParam);
  }
}

DWORD LayoutElement::dragThreadProc(LPVOID param)
{
  // current message
  MSG msg;
  // drag window class
  WNDCLASSEX wndClass;
  memset(&wndClass, 0, sizeof(wndClass));

  // set drag window class properties
  wndClass.cbSize         = sizeof(wndClass);
  wndClass.hInstance      = GetModuleHandle(NULL);
  wndClass.lpfnWndProc    = dragWndProc;
  wndClass.lpszClassName  = szDragWindow;

  // register drag window class
  RegisterClassEx(&wndClass);
  // create drag windows
  dragRoot->createDragWindows(dragRoot, FALSE, dragRoot->handle);

  // drag thread is ready
  SetEvent(layoutEvent);

  // enter thread message loop
  while (GetMessage(&msg, NULL, 0, 0) > 0)
  {
    TranslateMessage(&msg);
    DispatchMessage(&msg);
  }

  // destroy drag windows
  dragRoot->destroyDragWindows();
  // unregister drag window class
  UnregisterClass(wndClass.lpszClassName, wndClass.hInstance);

  return 0;
}

DWORD LayoutElement::resizeThreadProc(LPVOID param)
{
  // resized element
  LayoutElement* elem = (LayoutElement*)param;
  // resize window class
  WNDCLASSEX wndClass;
  memset(&wndClass, 0, sizeof(wndClass));

  // set resize window class properties
  wndClass.cbSize         = sizeof(wndClass);
  wndClass.hInstance      = GetModuleHandle(NULL);
  wndClass.lpszClassName  = szResizeWindow;
  wndClass.lpfnWndProc    = DefWindowProc;
  wndClass.hbrBackground  = CreateSolidBrush(0x00000000);

  // register resize window class
  RegisterClassEx(&wndClass);

  // create resize window
  resizeWindow = CreateWindowEx(
    WS_EX_NOACTIVATE | WS_EX_TOPMOST | WS_EX_LAYERED,
    szResizeWindow, _T(""), WS_POPUP,
    resizeBounds.x, resizeBounds.y,
    resizeBounds.width, resizeBounds.height,
    NULL, NULL, wndClass.hInstance, NULL);

  // show resize window without activating it
  ShowWindow(resizeWindow, SW_SHOWNOACTIVATE);
  SetLayeredWindowAttributes(resizeWindow, 0, 0x7F, LWA_ALPHA);

  // resize thread is ready
  SetEvent(layoutEvent);

  // current message
  MSG msg;

  // enter thread message loop
  while (GetMessage(&msg, NULL, 0, 0) > 0)
  {
    TranslateMessage(&msg);
    DispatchMessage(&msg);
  }

  // destroy resize window
  DestroyWindow(resizeWindow);
  // unregister resize window class
  UnregisterClass(szResizeWindow, wndClass.hInstance);

  return 0;
}

void  LayoutElement::doDrag(
  LayoutElement* target,
  LayoutDock dock
  )
{
  // if source is target, abort
  if (dragSourceHandle == target->handle)
    return;

  // remove source element from layout
  LayoutElement* elem = dragRoot->removeElement(dragSourceHandle);

  if (elem)
  {
    // add source element to target
    elem->dock = dock;

    // if set to fill
    if (dock == DockFill)
    {
      // add at the end of the list
      target->addElement(elem);
    }
    else
    {
      // add at the beggining of the list
      elem->next = target->childs;
      target->childs = elem;
    }

    // clean layout
    dragRoot->clean();

    // if target is window
    if (target->handle && target != dragRoot)
    {
      LayoutElement* child = new LayoutElement();

      // create a child copy of target element
      *child = *target;
      child->next = child->childs = NULL;
      child->dock = DockFill;
      target->addElement(child);

      // set target element as container
      target->handle = NULL;
      target->style = LayoutContainer;
      target->padding.setEmpty();
      target->margins.setEmpty();

      if (elem->isDockHorizontal())
      {
        // add child width to element width
        target->size.cx += elem->size.cx +
          elem->margins.getHoriz() + child->margins.getHoriz();
        // add child vertical margins to element height
        target->size.cy += child->margins.getVert();
      }
      else if (elem->isDockVertical())
      {
        // add child horizontal margins to element width
        target->size.cx += child->margins.getHoriz();
        // add child height to element height
        target->size.cy += elem->size.cy +
          elem->margins.getVert() + child->margins.getVert();
      }
    }
  }
}

void  LayoutElement::doLayout(
  HWND parent,
  Geom::Rect &rect
  )
{
  // processed element
  LayoutElement *elem = childs;
  // defer window data
  HDWP defer;
  // child window count
  UINT wndCount = 0;

  // apply padding
  padding.apply(rect);

  // count child windows
  while (elem)
  {
    if (elem->handle && !(elem->flags & LAYOUT_HIDDEN)) 
      ++wndCount;

    elem = elem->next;
  }

  // prepare window reposition
  defer = BeginDeferWindowPos(wndCount);
  elem = childs;

  // reset filled flag
  fillElement = NULL;

  // for each children
  while (elem)
  {
    // if element hidden
    if (elem->flags & LAYOUT_HIDDEN || parent == (HWND)-1)
    {
      // empty element bounds
      elem->bounds.setEmpty();

      // set filled flag
      if (elem->dock == DockFill)
        fillElement = elem->handle ? elem->handle : (HWND)-1;

      // hide element window
      ShowWindow(elem->handle, SW_HIDE);

      // hide child windows if not window
      if (!elem->handle) 
        elem->doLayout((HWND)-1, rect);
    }
    else
    {
      // apply horizontal anchors
      if ((elem->flags & LAYOUT_ANCHOR_LEFT) ||
        (elem->flags & LAYOUT_ANCHOR_RIGHT))
      {
        if (!(elem->flags & LAYOUT_ANCHOR_LEFT))
        {
          // right anchor
          elem->position.x += (rect.getWidth() - visible.cx);
        }
        else if (elem->flags & LAYOUT_ANCHOR_RIGHT)
        {
          // left & right anchors
          elem->size.cx += (rect.getWidth() - visible.cx);
        }
      }

      // apply horizontal center anchor
      if (elem->flags & LAYOUT_ANCHOR_HCENTER)
        elem->position.x = (rect.getWidth() - elem->size.cx) / 2 + rect.left;

      // apply vertical anchors
      if ((elem->flags & LAYOUT_ANCHOR_TOP) ||
        (elem->flags & LAYOUT_ANCHOR_BOTTOM))
      {
        if (!(elem->flags & LAYOUT_ANCHOR_TOP))
        {
          // bottom anchor
          elem->position.y += (rect.getHeight() - visible.cy);
        }
        else if (elem->flags & LAYOUT_ANCHOR_BOTTOM)
        {
          // top & bottom anchors
          elem->size.cy += (rect.getHeight() - visible.cy);
        }
      }

      // apply vertical center anchor
      if (elem->flags & LAYOUT_ANCHOR_VCENTER)
        elem->position.y = (rect.getHeight() - elem->size.cy) / 2 + rect.top;

      if (elem->flags & LAYOUT_NORESIZE)
      {
        elem->bounds.size = Size::fromWindow(elem->handle);
      }
      else
      {
        // set initial size
        elem->bounds.size = elem->size;
      }

      // set minimum and maximum size
      if (elem->style != LayoutFixed)
      {
        // maximum size available for element
        Size maximum(rect.getWidth(), rect.getHeight());

        // make sure side-docked element does not hide its parent
        if (style != LayoutFixed)
        {
          if (elem->isDockHorizontal())
            maximum.cx -= DOCK_MINWIDTH;
          else if (elem->isDockVertical())
            maximum.cy -= DOCK_MINHEIGHT;
        }

        if ((elem->flags & LAYOUT_ANCHOR_HCENTER) != LAYOUT_ANCHOR_HCENTER)
          elem->bounds.size.cx = 
            min(maximum.cx, max(DOCK_MINWIDTH, elem->size.cx));

        if ((elem->flags & LAYOUT_ANCHOR_VCENTER) != LAYOUT_ANCHOR_VCENTER)
          elem->bounds.size.cy = 
            min(maximum.cy, max(DOCK_MINHEIGHT, elem->size.cy));
      }

      // calculate updated element bounds
      switch (elem->dock)
      {
      case DockLeft:
        elem->bounds = Bounds(
          rect.left + elem->margins.left,
          rect.top + elem->margins.top,
          elem->bounds.size.cx,
          rect.getHeight() - elem->margins.getVert());

        rect.left += elem->margins.getHoriz() + elem->bounds.size.cx;
        if (style != LayoutFixed) rect.left += SIZEBAR_SIZE;
        break;
      case DockRight:
        elem->bounds = Bounds(
          rect.right - elem->bounds.size.cx - elem->margins.right,
          rect.top + elem->margins.top,
          elem->bounds.size.cx,
          rect.getHeight() - elem->margins.getVert());

        rect.right -= elem->margins.getHoriz() + elem->bounds.size.cx;
        if (style != LayoutFixed) rect.right -= SIZEBAR_SIZE;
        break;
      case DockTop:
        elem->bounds = Bounds(
          rect.left + elem->margins.left,
          rect.top + elem->margins.top,
          rect.getWidth() - elem->margins.getHoriz(),
          elem->bounds.size.cy);

        rect.top += elem->margins.getVert() + elem->bounds.size.cy;
        if (style != LayoutFixed) rect.top += SIZEBAR_SIZE;
        break;
      case DockBottom:
        elem->bounds = Bounds(
          rect.left + elem->margins.left,
          rect.bottom - elem->bounds.size.cy - elem->margins.bottom,
          rect.getWidth() - elem->margins.getHoriz(),
          elem->bounds.size.cy);

        rect.bottom -= elem->margins.getVert() + elem->bounds.size.cy;
        if (style != LayoutFixed) rect.bottom -= SIZEBAR_SIZE;
        break;
      case DockFill:
        elem->bounds = Bounds(
          rect.left + elem->margins.left,
          rect.top + elem->margins.top,
          rect.getWidth() - elem->margins.getHoriz(),
          rect.getHeight() - elem->margins.getVert());

        rect.right = rect.left;
        rect.bottom = rect.top;

        // set filled flag
        fillElement = elem->handle ? elem->handle : (HWND)-1;
        break;
      case DockNone:
        elem->bounds.point = elem->position;
        break;
      }

      // if element is window
      if (elem->handle)
      {
        Geom::Rect currentRect;
        Geom::Bounds currentBounds;

        // get current bounds
        GetWindowRect(elem->handle, &currentRect);
        currentRect.toBounds(&currentBounds);
        ScreenToClient(parent, &currentBounds.point);

        // show element window
        ShowWindow(elem->handle, SW_SHOWNOACTIVATE);

        // update parent window
        if (GetParent(elem->handle) != parent)
          SetParent(elem->handle, parent);

        // set element bounds
        if (currentBounds != elem->bounds)
        {
          if (currentBounds.size == elem->bounds.size)
          {
            // same size, only move
            defer = DeferWindowPos(defer, elem->handle, NULL,
              elem->bounds.x, elem->bounds.y, 0, 0,
              SWP_NOZORDER | SWP_NOSIZE);
          }
          else if (currentBounds.point == elem->bounds.point)
          {
            // same position, only size
            defer = DeferWindowPos(defer, elem->handle, NULL,
              0, 0,  elem->bounds.cx, elem->bounds.cy,
              SWP_NOZORDER | SWP_NOMOVE);
          }
          else
          {
            // move and size
            defer = DeferWindowPos(defer, elem->handle, NULL,
              elem->bounds.x, elem->bounds.y,
              elem->bounds.cx, elem->bounds.cy,
              SWP_NOZORDER);
          }
        }

        // do absolute layout
        elem->doLayout(elem->handle,
          Rect(0, 0, elem->bounds.cx, elem->bounds.cy));
        // set visible bounds
        elem->visible = elem->bounds;
      }
      else
      {
        // element client bounds
        Geom::Rect client(elem->bounds);

        // do relative layout
        elem->doLayout(parent, client);
        // set visible bounds
        client.toBounds(&elem->visible);
      }
    }

    // process next child
    elem = elem->next;
  }

  // update window positions and sizes
  EndDeferWindowPos(defer);
}

void  LayoutElement::destroy(LayoutElement* element)

{
  // clear next element
  if (element->next)   destroy(element->next);
  // clear element children
  if (element->childs) destroy(element->childs);
  // destroy associated window
  if (element->handle) DestroyWindow(element->handle);

  // delete element
  delete element;
}

void  LayoutElement::clear(LayoutElement* element)
{
  while (element)
  {
    LayoutElement* next = element->next;

    clear(element->childs);
    ShowWindow(element->handle, SW_HIDE);

    delete element;
    element = next;
  }
}

void  LayoutElement::clean()
{
  // processed element
  LayoutElement* elem = childs;
  // previous element
  LayoutElement* prev = NULL;
  // at least one child is set to fill
  BOOL bFilled = FALSE;

  // for each child
  while (elem)
  {
    // clean child
    elem->clean();

    // if element is set to fill, set filled flag
    if (elem->dock == DockFill)
      bFilled = TRUE;

    // process next element
    prev = elem;
    elem = elem->next;
  }

  // if no element set to fill, children list not empty and
  // layout is a container, set last child dock to fill
  if (!bFilled && prev && style == LayoutContainer)
    prev->dock = DockFill;
}

void  LayoutElement::beginDrag(HWND source)
{
  // if no drag thread running
  if (!dragThread)
  {
    // if drag source found
    if (dragSource = findElement(source))
    {
      // set drag data
      dragRoot    = this;
      dragSourceHandle  = source;
      dragTarget  = NULL;
      dragDock    = DockNone;

      // create layout synchronization event
      layoutEvent = CreateEvent(NULL, FALSE, FALSE, szLayoutEvent);

      // start drag thread
      dragThread = CreateThread(NULL, 0,
        dragThreadProc, NULL, 0, &dragThreadID);

      // wait for resize thread to be ready
      WaitForSingleObject(layoutEvent, INFINITE);
    }
  }
}

LayoutDock  LayoutElement::endDrag(BOOL cancel)
{
  // if drag thread running
  if (dragThread)
  {
    // wait for drag thread to terminate
    PostThreadMessage(dragThreadID, WM_QUIT, 0, 0);
    WaitForSingleObject(dragThread, INFINITE);

    // if drag not cancelled, perform drag
    if (!cancel && dragDock)
      doDrag(dragTarget, dragDock);

    // close drag thread
    CloseHandle(dragThread);
    dragThread = NULL;

    // return result
    return dragDock;
  }

  // no drag active
  return DockNone;
}

void  LayoutElement::sendMouseDrag(UINT msg, Point mouse)
{
  // processed element
  LayoutElement* child = childs;

  // for each child
  while (child)
  {
    // if contains drag window
    if (child->dragWindow)
    {
      // get client window mouse coordinates
      Point client = mouse.toClient(child->dragWindow);

      // send mouse message to window
      PostMessage(child->dragWindow, msg,
        (WPARAM)MK_LBUTTON,
        (LPARAM)(((client.y << 16) + client.x))
        );
    }

    // process child
    child->sendMouseDrag(msg, mouse);
    // process next element
    child = child->next;
  }
}

void  LayoutElement::drag()
{
  if (dragThread)
    sendMouseDrag(WM_MOUSEMOVE, Geom::Point::fromCursor());
}

void  LayoutElement::createDragWindows(
  LayoutElement* owner,
  BOOL parentFixed,
  HWND window)
{
  if ((!parentFixed || style != LayoutFixed) && !(flags & LAYOUT_HIDDEN))
  {
    // drag window position
    Geom::Point dest = visible.point;
    // create drag window data
    DragWindowData *data = new DragWindowData;

    // get drag window screen coordinates
    ClientToScreen(window, &dest);

    // set drag window data
    data->layout    = handle == dragSourceHandle ? owner : this;
    data->cache     = new Gdip::Bitmap(visible.cx, visible.cy);
    data->flags     = handle == dragSourceHandle ? DRAGWINDOWFLAGS_ROOT : 0;
    data->dock      = DockNone;

    // create drag window
    dragWindow = CreateWindowEx(
      WS_EX_NOACTIVATE | WS_EX_TOPMOST | WS_EX_LAYERED,
      szDragWindow, _T(""), WS_POPUP,
      dest.x, dest.y, visible.cx, visible.cy,
      NULL, NULL, GetModuleHandle(NULL), data);

    // update and show drag window without activating it
    renderDragWindow(data);
    dragWndUpdate(dragWindow);
    ShowWindow(dragWindow, SW_SHOWNOACTIVATE);
  }
  else
  {
    dragWindow = NULL;
  }

  // processed element
  LayoutElement* elem = childs;

  // process children
  while (elem)
  {
    elem->createDragWindows(
      style == LayoutDrag ? this : owner,
      style == LayoutFixed,
      handle ? handle : window);

    // process next child
    elem = elem->next;
  }
}

void  LayoutElement::destroyDragWindows()
{
  // if element has drag window
  if (dragWindow)
  {
    // destroy drag window
    DestroyWindow(dragWindow);
    dragWindow = NULL;
  }

  // process children
  if (childs) childs->destroyDragWindows();
  // process next element
  if (next) next->destroyDragWindows();
}

void  LayoutElement::calcSize(
  SIZE &out,
  LONG fillX,
  LONG fillY
  )
{
  LayoutElement* elem = childs;

  out = Size(padding.getHoriz(), padding.getVert());

  while (elem)
  {
    // if element not hidden
    if (!(elem->flags & LAYOUT_HIDDEN))
    {
      out.cx += elem->margins.getHoriz();
      out.cy += elem->margins.getVert();

      if (elem->dock == DockFill)
      {
        out.cx += fillX;
        out.cy += fillY;
      } else if (elem->dock != DockNone)
      {
        Geom::Size size = Size::fromWindow(elem->handle);

        if (elem->isDockHorizontal())
          out.cx += elem->size.cx > 0 ? elem->size.cx : size.cx;
        if (elem->isDockVertical())
          out.cy += elem->size.cy > 0 ? elem->size.cy : size.cy;
      }
    }

    // process next child
    elem = elem->next;
  }
}

LayoutElement*  LayoutElement::findResize(
  Point mouse,
  Bounds &sizeBar,
  LayoutElement** parent
  )
{
  // processed element
  LayoutElement* elem = childs;
  // element found
  LayoutElement* target = NULL;

  // for each child
  while (elem)
  {
    // if layout is not fixed
    if (style != LayoutFixed)
    {
      // calculate sizebar bounds
      switch (elem->dock)
      {
      case DockLeft:
        sizeBar = Bounds(
          elem->bounds.x + elem->bounds.cx,
          elem->bounds.y,
          SIZEBAR_SIZE,
          elem->bounds.cy);
        break;
      case DockRight:
        sizeBar = Bounds(
          elem->bounds.x - SIZEBAR_SIZE,
          elem->bounds.y,
          SIZEBAR_SIZE,
          elem->bounds.cy);
        break;
      case DockTop:
        sizeBar = Bounds(
          elem->bounds.x,
          elem->bounds.y + elem->bounds.cy,
          elem->bounds.cx,
          SIZEBAR_SIZE);
        break;
      case DockBottom:
        sizeBar = Bounds(
          elem->bounds.x,
          elem->bounds.y - SIZEBAR_SIZE,
          elem->bounds.cx,
          SIZEBAR_SIZE);
        break;
      default:
        sizeBar.setEmpty();
        break;
      }

      // if mouse is over sizebar, return result
      if (sizeBar.contains(mouse))
      {
        if (parent) *parent = this;
        return elem;
      }
    }

    // if element is not a window
    if (!elem->handle)
    {
      // if found in element children list, return result
      if (target = elem->findResize(mouse, sizeBar, parent))
        return target;
    }

    // process next child
    elem = elem->next;
  }

  // element not found
  return NULL;
}

void  LayoutElement::setResizeCursor(Point mouse)
{
  // sizebar bounds
  Bounds sizeBar;
  // displayed cursor
  LPCTSTR cursorID = IDC_ARROW;
  // sizebar owner element
  LayoutElement* elem = resizeElem ?
resizeElem : findResize(mouse, sizeBar, NULL);

  // if element found
  if (elem)
  {
    // find corresponding cursor
    if (elem->isDockHorizontal())
      cursorID = IDC_SIZEWE;
    else if (elem->isDockVertical())
      cursorID = IDC_SIZENS;
  }

  // display appropriate cursor
  SetCursor(LoadCursor(NULL, cursorID));
}

BOOL  LayoutElement::beginResize(HWND parent, Point mouse)
{
  // if not resizing
  if (!resizeThread)
  {
    // if resized element found
    if (resizeElem = findResize(mouse, resizeBounds, &resizeParent))
    {
      // find resize window screen coordinates
      ClientToScreen(parent, &resizeBounds.point);

      // store old element size
      if (resizeElem->isDockHorizontal())
        resizeOld = max(resizeElem->size.cx, DOCK_MINWIDTH);
      else
        resizeOld = max(resizeElem->size.cy, DOCK_MINHEIGHT);

      // calculate resize limits
      resizeLimits = resizeParent->bounds;
      resizeLimits.stretch(-SIZEBAR_SIZE, -SIZEBAR_SIZE);
      resizeLimits.inflate(-DOCK_MINWIDTH, -DOCK_MINHEIGHT);
      resizeParent->padding.apply(resizeLimits);

      // store start mouse position
      resizeStart = mouse;
      // display appropriate cursor
      setResizeCursor(mouse);

      // create layout synchronization event
      layoutEvent = CreateEvent(NULL, FALSE, FALSE, szLayoutEvent);

      // create resize thread
      resizeThread = CreateThread(NULL, 0,
        resizeThreadProc, NULL, 0, &resizeThreadID);

      // wait for resize thread to be ready
      WaitForSingleObject(layoutEvent, INFINITE);

      // resize started
      return TRUE;
    }
  }

  // resize failed
  return FALSE;
}

BOOL  LayoutElement::endResize(BOOL cancel)
{
  // if resizing
  if (resizeThread)
  {
    // wait for drag thread to terminate
    PostThreadMessage(resizeThreadID, WM_QUIT, 0, 0);
    WaitForSingleObject(resizeThread, INFINITE);

    // close drag thread
    CloseHandle(resizeThread);
    resizeThread = NULL;

    // if resize not cancelled
    if (!cancel)
    {
      // set new element size
      if (resizeElem->isDockHorizontal())
        resizeElem->size.cx = resizeNew;
      else
        resizeElem->size.cy = resizeNew;
    }

    // resize successfull
    resizeElem = NULL;
    CloseHandle(layoutEvent);
    return TRUE;
  }

  // resize failed
  return FALSE;
}

BOOL  LayoutElement::resize(Point mouse)
{
  // if resizing
  if (resizeThread)
  {
    // limit horizontal mouse coordinate
    if (mouse.x < resizeLimits.x)
      mouse.x = resizeLimits.x;
    else if (mouse.x > resizeLimits.x + resizeLimits.cx)
      mouse.x = resizeLimits.x + resizeLimits.cx;

    // limit vertical mouse coordinate
    if (mouse.y < resizeLimits.y)
      mouse.y = resizeLimits.y;
    else if (mouse.y > resizeLimits.y + resizeLimits.cy)
      mouse.y = resizeLimits.y + resizeLimits.cy;

    // move resize window
    if (resizeElem->isDockHorizontal())
    {
      SetWindowPos(resizeWindow, NULL,
        resizeBounds.x + (mouse.x - resizeStart.x), resizeBounds.y,
        0, 0, SWP_NOSIZE | SWP_NOACTIVATE | SWP_SHOWWINDOW);
    }
    else
    {
      SetWindowPos(resizeWindow, NULL,
        resizeBounds.x, resizeBounds.y + (mouse.y - resizeStart.y),
        0, 0, SWP_NOSIZE | SWP_NOACTIVATE | SWP_SHOWWINDOW);
    }

    // calculate element new size
    switch (resizeElem->dock)
    {
    case DockLeft:
      resizeNew = resizeOld + (mouse.x - resizeStart.x);
      resizeNew = max(DOCK_MINWIDTH, resizeNew);
      break;
    case DockRight:
      resizeNew = resizeOld + (resizeStart.x - mouse.x);
      resizeNew = max(DOCK_MINWIDTH, resizeNew);
      break;
    case DockTop:
      resizeNew = resizeOld + (mouse.y - resizeStart.y);
      resizeNew = max(DOCK_MINHEIGHT, resizeNew);
      break;
    case DockBottom:
      resizeNew = resizeOld + (resizeStart.y - mouse.y);
      resizeNew = max(DOCK_MINHEIGHT, resizeNew);
      break;
    }

    // resize successfull
    return TRUE;
  }

  // display appropriate cursor
  setResizeCursor(mouse);

  // resize failed
  return FALSE;
}

VOID  LayoutElement::count(INT &total)
{
  LayoutElement* child = childs;

  while (child)
  {
    child->count(++total);
    child = child->next;
  }
}

VOID  LayoutElement::write(
  StoredLayout* &ptr,
  LayoutWindowToID proc
  )
{
  LayoutElement* child = childs;

  while (child)
  {
    memcpy(ptr, child, sizeof(LayoutHeader));
    ptr->flags = 0;
    ptr->id = proc(child->handle);

    if (child->childs)
      ptr->flags |= STOREDLAYOUTFLAGS_CHILDS;
    if (!child->next)
      ptr->flags |= STOREDLAYOUTFLAGS_END;

    child->write(++ptr, proc);
    child = child->getNext();
  }
}

VOID  LayoutElement::read(
  StoredLayout* &ptr,
  LayoutIDToWindow proc
  )
{
  StoredLayout* current;

  do
  {
    LayoutElement* child = new LayoutElement;

    memcpy(child, ptr, sizeof(LayoutHeader));
    child->handle = proc(ptr->id);
    child->next = child->childs = NULL;
    child->flags &= LAYOUT_FLAGSMASK;

    addElement(child);
    current = (ptr++);

    if (current->flags & STOREDLAYOUTFLAGS_CHILDS)
      child->read(ptr, proc);
  }
  while (!(current->flags & STOREDLAYOUTFLAGS_END));
}

VOID  LayoutElement::save(
  BYTE* &buffer,
  DWORD* length,
  LayoutWindowToID proc
  )
{
  StoredLayout* layout;
  StoredLayout* ptr;
  INT total = 0;

  count(total);

  ptr = layout = new StoredLayout[total];
  write(ptr, proc);

  buffer = (BYTE*)layout;
  *length = sizeof(StoredLayout) * total;
}
